/*
 * BarometerTerminal.pde
 *
 * Read the Barometric Pressure and Temperature values from SCP1000 sensor
 * in every 30 minutes and send data to Serial.
 * Now support 4 sevent-segment LED to display current senasor data
 * (temperature and barometric pressure in hPa).
 * Data is stored in EEPROM and send all data from latest to oldest
 * via serial port on startup.
 * 
 * based on sample code from Arduino forum
 * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1236443720/8
 *
 * HISTORY
 * 
 * 2010/7/7 change interval (30min)
 * 2010/7/29 add 7 segments display support
 *           now we use 2 byte for one sensor data 
 * 2010/8/3  add function to display temprature
 * 2010/8/6  internal timer control function was replaced by Metro library.
 * 2010/8/23 modify comment
 * 2010/9/14 8x8 led matrix support(experimental)
 * 2010/9/15 8x8 comment updateed
 * 2010/9/22 PIN assign changed
 * 2010/10/24 force readig SW 
 * 2012/4/23 update for Arduion1.0
 *           force standby after reading sensor (for energy saving)
 *
 * LIBRARY
 * Arduino: Playground - Metro
 * http://www.arduino.cc/playground/Code/Metro
 *
 * Arduino Forum > Hardware > Interfacing > How to connect SCP1000 pressure sensor
 * http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1236443720/8
 *
 * Abount MPL115A implementation
 *
 * original sampple code by GarretLab 
 * from http://garretlab.web.fc2.com/arduino/lab/barometer_sensor/index.html
 * templaure calcuration from https://github.com/misenso/MPL115A2-Arduino-Library/blob/master/MPL115A2.cpp
 * <script src='http://pastie.org/3939144.js'></script>
 */

/*
 * Copyright (c) 2010 elekid
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */

#include <Metro.h>
#include <EEPROM.h>
#include <Wire.h>


//---------------------------------------------
// pin usage definition
//---------------------------------------------
//  ***** Arduino board layout ******
// +-------------------------------------+
// | 13 12 11 10 9 8     7 6 5 4 3 2 1 0 |
// |                                 T R |
// |          DIGITAL IN/OUT         X X |
// |                                     |
// |     A r d u i n o                   |
// |        D u e m i l a n o v e        |
// |                                     |
// |                                     |
// |                          ANALOG IN  |
// |                         0 1 2 3 4 5 |
// +-------------------------------------+


// Specify slave select pin for SCP1000 device
//   also following PINS are used in library
//     11,12,13 (OUT,IN,CLOCK) 


// #define LED_PIN1 14
// for 7 segments x 4 display
#define CLOCK_PIN 2
#define DATA_PIN  3

// for shift registors controlling 8x8 LED matrix
#define A_CLOCK_PIN 4
#define A_DATA_PIN  5

#define K_CLOCK_PIN 6
#define K_DATA_PIN  7
#define K_LATCH_PIN 8  // for 74HC595 ONLY

// for data send button
#define SEND_ON_PIN 14

// for 7 segments x 4 display
// D16-->A2, D17-->A3, D12, D13 (see above fig)
int TR[4] = {16,17,12,13,};

// FOR I2C
const int I2CADDR = 0x60;



// <<data diagram of EEPROM>>
// 
//   #  |data description
// -----+-----------------------------------------------
//   0  | current EEPROM address  (MSB/Jyoui-bit)
//   1  | current EEPROM address  (LSB/kai-bit)
//   2  | current # of data in EEPROM (MSB/Jyoui-bit)
//   3  | current # of data in EEPROM (LSB/kai-bit)
//   4  | baro data #1(MSB/Jyoui-bit)
//   5  | baro data #1(LSB/kai-bit)
//   6  | baro data #1(in integer)
//   7  | temperature data #1
//   8  | baro data #2(MSB/Jyoui-bit)
//   9  | baro data #2(LSB/kai-bit)
//  10  | baro data #2 (in integer)
//  11  | temperature data #2
//       :
//       :
//       :
// 400  | baro data #100(MSB/Jyoui-bit)
// 401  | baro data #100(LSB/kai-bit)
// 402  | baro data #100(in integer)
// 403  | temperature data #100
// 
// 
// 
// 


//---------------------------------------------
// definition related data stored in EEPROM 
//---------------------------------------------

// precision of data stored in EEPROM ( 0.01 step)
#define EEPROM_BARO_PRECISION   100.0
// differnce of following value will be save (in above precision)
#define EEPROM_MIN_BARO         900
// number of data stored in EEPROM (EEPROM is 512 byte only)
#define EEPROM_MAX_DATA         100
// data will be stored from following address
#define EEPROM_MIN_ADDR          4
#define EEPROM_MAX_ADDR    EEPROM_MIN_ADDR + EEPROM_MAX_DATA*4 - 4

// addr 0 and 1 will be used 
#define EEPROM_ADDR_STORED   0
// addr 2 and 3 will be used 
#define EEPROM_CNT_STORED    2

// #define SAMPLING_RATE 6
#define SAMPLING_RATE 2
#define GRAPH_BUF_SIZE   8*SAMPLING_RATE


#define MID_BARO_MATRIX 850
#define MID_TEMP_MATRIX 100


//---------------------------------------------
// seven segments LED
//---------------------------------------------
//  
//   aaaaaaaa
//  f        b
//  f        b
//  f        b
//   gggggggg
//  e        c
//  e        c
//  e        c
//   dddddddd     DP
//
//  abcdefgD
// B00000000
//
// definition of 7 segment patterns
byte NUM[13] = {
  B00000011,  // 0
  B10011111,  // 1
  B00100101,  // 2
  B00001101,  // 3
  B10011001,  // 4
  B01001001,  // 5
  B11000001,  // 6
  B00011111,  // 7 
  B00000001,  // 8
  B00011001,  // 9
  B11111111,  // all off
  B11100101,  // "c" for celcius
  B11111101,  // "-" for minus sign
};

// index of array NUM[]
#define MINUS_SIGN 12
#define CELCIUS 11
#define ALL_OFF 10

//---------------------------------------------
// Timer difinition
//---------------------------------------------
// loop() counter for switching 7seg LED
#define SWITCH_SEG_COUNT 99

// loop() counter for switching 8x8 matrix
#define SWITCH_MATRIX_COUNT 100

// 1806832UL (milsec) is 30 minutes
#define DATA_SEND_PERIOD 1800262UL

#define SWITCH_DISP_PERIOD 4000
#define SWITCH_HPA_PERIOD  2000
#define MEASURE_ONLY_PERIOD 120000UL  // 120sec 




//---------------------------------------------
// grobal variables
//---------------------------------------------

unsigned long switchSegCounter=SWITCH_SEG_COUNT;
unsigned long switchMatrixCounter=SWITCH_MATRIX_COUNT;


byte SEG[4];
int currentSegID =3 ;

byte TempOrBaroFlg=0;



// for 8x8 trend chart display data storing
//   - Barometer
int P[GRAPH_BUF_SIZE];
//   - Temperature
int T[GRAPH_BUF_SIZE];
int graphBufCounter = 0 ;

int M[8];
int matrixColCounter=0;

// matrix display buffer
byte Matrix[8] = {
B00000000 ,
B00000000 ,
B00000000 ,
B00000000 ,
B00000000 ,
B00000000 ,
B00000000 ,
B00000000 
};



unsigned int currentBaro = 0;
unsigned int currentTemp = 0;

// for MLP115A
// internal registers
float a0, b1, b2, c12, c11, c22;
// sensor data
float Pha;
float tempv;



//---------------------------------------------
// ibrary definition
//---------------------------------------------

// SCP1000
// SCP1000 scp1000(SELECT_PIN);

// Timer
Metro dataSendTimer = Metro(DATA_SEND_PERIOD);
Metro switchDispTimer = Metro(SWITCH_DISP_PERIOD);
Metro measureOnlyTimer = Metro(MEASURE_ONLY_PERIOD);



//---------------------------------------------
// setup()
//---------------------------------------------
void setup()
{

  Serial.begin(9600); // Open serial connection to report values to host
  Serial.println("DEBUG Starting up");

  Wire.begin();
  
  Wire.beginTransmission(I2CADDR);
  Wire.write(0x04);        // Read coefficient data
  Wire.endTransmission();
  
  Wire.requestFrom(I2CADDR, 12);  // Request 12 bytes
  if (Wire.available()) {
    a0 = read_coefficients(16, 3, 0);
    b1 = read_coefficients(16, 13, 0);
    b2 = read_coefficients(16, 14, 0);
    c12 = read_coefficients(14, 13, 9);
    c11 = read_coefficients(11, 10, 11);
    c22 = read_coefficients(11, 10, 15);
  }
  
  Serial.println("DEBUG Wire initialized.");
  
  // pinMode(LED_PIN1, OUTPUT);

  pinMode(CLOCK_PIN, OUTPUT);
  pinMode(DATA_PIN,  OUTPUT);

  pinMode(A_CLOCK_PIN, OUTPUT);
  pinMode(A_DATA_PIN, OUTPUT);
  pinMode(K_CLOCK_PIN, OUTPUT);
  pinMode(K_DATA_PIN, OUTPUT);
  pinMode(K_LATCH_PIN, OUTPUT);
  pinMode(SEND_ON_PIN, INPUT);

  for (int i = 0 ; i < 4 ; i++){
    pinMode(TR[i],  OUTPUT);    
    digitalWrite(TR[i], HIGH);
  }

  delay(1000);
  listEepromData();
  delay(1000);

  //scp1000.init();
  delay(500);
  readSensor();
//  recordData();
//  sendData();

}

//---------------------------------------------
// loop()
//---------------------------------------------
void loop()
{
  if ( dataSendTimer.check() == 1 ) {
    readSensor();
    recordData();
    sendData();
  } 
  else if ( measureOnlyTimer.check() == 1 ) {
    readSensor();
  } 
  else if ( switchDispTimer.check() == 1 ) {
    switchBaroTemp();
  }
  
  if ( digitalRead(SEND_ON_PIN) == LOW ) {
    delay(250);
    readSensor();
    Serial.println("DEBUG FORCE READING");
    recordData();
    sendData();
  }
  
  // update 7 segment LED
  checkAndSwitchSegment();
  // update 8x8 matrix LED
  checkAndSwitchMatrixRow();

}


//---------------------------------------------
// readSensor
//---------------------------------------------
void readSensor() {

  Wire.beginTransmission(I2CADDR);
  Wire.write(0x12);      // Start both conversions(Pressure and Temperature)
  Wire.write(0x01);
  Wire.endTransmission();

  delay(5);

  Wire.beginTransmission(I2CADDR);
  Wire.write((uint8_t)0x00);  // Read pressure and temperature
  Wire.endTransmission();

  Wire.requestFrom(I2CADDR, 4); // Request 4 bytes

  if(Wire.available()) {
    unsigned int Padc = read_adc();
    unsigned int Tadc = read_adc();
  
    float Pcomp = a0 + (b1 + c11 * Padc + c12 * Tadc) * Padc + (b2 + c22 * Tadc) * Tadc;
    tempv=25 + (Tadc - 498.0) / -5.35;
    Pha = Pcomp * 650 / 1023 + 500;
    // type conversion to integer
    currentBaro =  int( Pha + 0.5);
    currentTemp = tempv ;

  }
  
  
  

  // digitalWrite(LED_PIN1,HIGH);

//  scp1000.setRunMode(); 
//  scp1000.resetSensor();
  delay(10);
//  scp1000.readSensor();
  delay(10);
//  scp1000.readSensor();
  delay(10);
//  scp1000.readSensor();
//  if (scp1000.BaroP < 800 ) {
//    delay(100);
//    scp1000.readSensor();
//  }

//  currentBaro = int(scp1000.BaroP + 0.5 ) ;
//  currentTemp = scp1000.TempC ;


  TempOrBaroFlg = 0;
  switchBaroTemp();
  
//  scp1000.setStandby();
  // digitalWrite(LED_PIN1,LOW);
}

//---------------------------------------------
// sendData
//---------------------------------------------
void sendData() {
//  Serial.print(scp1000.BaroP);
  Serial.print(Pha);
  Serial.print(" hPa ");
  Serial.print(tempv);
  Serial.println(" C");
}


//---------------------------------------------
// recordData
//---------------------------------------------
void recordData() {
  // get current EEPROM address
  int addr = getEepromInfo(EEPROM_ADDR_STORED);
  // get current number of data in EEPROM
  int cnt =  getEepromInfo(EEPROM_CNT_STORED);

  int new_addr =0;
  int new_cnt = 0;

  // very first time
  if (addr == 0) { 
    new_addr = EEPROM_MIN_ADDR;
    new_cnt = 1;
  } 
  else {
    new_addr = addr + 4 ;
    if (new_addr > EEPROM_MAX_ADDR ) {
      new_addr = EEPROM_MIN_ADDR;
    }
    new_cnt = cnt + 1;
    if (new_cnt > EEPROM_MAX_DATA) {
      new_cnt= EEPROM_MAX_DATA;
    }
  }
  int new_val=calcCompressedValue(Pha);
  putEepromInfo(new_addr, new_val);
  
  // store integer data for 8x8 matrix 
  // (data range is limited by size of 1 byte, ie 0～255)
  EEPROM.write(new_addr+2, currentBaro - MID_BARO_MATRIX);
  EEPROM.write(new_addr+3, currentTemp + MID_TEMP_MATRIX);

  putEepromInfo(EEPROM_ADDR_STORED, new_addr);
  putEepromInfo(EEPROM_CNT_STORED, new_cnt);

  shiftValues();

}

//---------------------------------------------
// switchBaroTemp() 
//---------------------------------------------
void switchBaroTemp() {
  if ( TempOrBaroFlg == 0 ) {
    TempOrBaroFlg = 1;
    // set 7seg for display barometer
    extractBaroDigit();
    // set 8x8 matrix for display barometer
    setBufToMatrix(P);
    genMatrixPattern();

    switchDispTimer.interval(SWITCH_DISP_PERIOD);
  } 
  else if (TempOrBaroFlg == 1 ) {
    TempOrBaroFlg = 0 ;
    // set 7seg for display temperature
    extractTempDigit();
    // set 8x8 matrix for display barometer
    setBufToMatrix(T);
    genMatrixPattern();
    
    switchDispTimer.interval(SWITCH_DISP_PERIOD);
  }
  /*      
   } else if (TempOrBaroFlg == 1 ) {
   TempOrBaroFlg = 2;
   SEG[3]=B11010001;
   SEG[2]=B00110001;
   SEG[1]=B00010001;
   SEG[0]=B11111111;
   switchDispTimer.interval(SWITCH_HPA_PERIOD);
   */
}

//---------------------------------------------
// extractBaroDigit()
//---------------------------------------------
void extractBaroDigit(){
  SEG[3] = currentBaro / 1000 ;
  SEG[2] = (currentBaro - (1000 * SEG[3])) / 100 ;
  SEG[1] = (currentBaro - (1000 * SEG[3]) - (100 * SEG[2])) / 10 ;
  SEG[0] = (currentBaro - (1000 * SEG[3]) - (100 * SEG[2]) - (10 * SEG[1]));

//  Serial.print( "currentBaro ");
//  Serial.println(currentBaro);
}

//---------------------------------------------
// extractTempDigit()
//---------------------------------------------
void extractTempDigit() {
  // bit complicated here. becase i want display 24c instead of 024c 
  // when temperature is 24 degrees celcius
  if (currentTemp < 0 ) {
    SEG[3] = MINUS_SIGN;
  } 
  else { 
    SEG[3] = ALL_OFF;
  }
  if (  abs (currentTemp ) >= 100 ){
    SEG[2] = 9;
    SEG[1] = 9;
  } 
  else {
    if ( abs (currentTemp) >= 10 ){
      SEG[2] = currentTemp / 10 ;
      SEG[1] = abs (currentTemp) - SEG[2]*10;
    } 
    else { 
      SEG[2] = ALL_OFF;
      SEG[1] = abs (currentTemp);
    }
  }
  SEG[0] = CELCIUS ;
}

//---------------------------------------------
// shiftValues()
//---------------------------------------------
void shiftValues() {
  for ( int i = GRAPH_BUF_SIZE-1 ; i > 0 ; i--) {
    P[i] = P[i-1];
    T[i] = T[i-1];
  }

  P[0] = currentBaro;
  T[0] = currentTemp;
  
  if (graphBufCounter < GRAPH_BUF_SIZE ) {
     graphBufCounter++ ;
  }


}


//---------------------------------------------
// getEepromInfo()
//---------------------------------------------
int getEepromInfo(int addr){
  byte b1 = EEPROM.read(addr);
  byte b2 = EEPROM.read(addr+1);
  int b = b1 << 8 | b2 ;
  return(b);
}

//---------------------------------------------
// putEepromInfo
//---------------------------------------------
void putEepromInfo(int addr , int value) {
  byte b = value >> 8 & 0x00FF;
  EEPROM.write(addr, b);
  b = value & 0x00FF;
  EEPROM.write(addr+1,b);

}

//---------------------------------------------
// calcCompressedValue
//---------------------------------------------
int calcCompressedValue(float baro) {
  long new_value_long = baro * EEPROM_BARO_PRECISION -  EEPROM_MIN_BARO * EEPROM_BARO_PRECISION;
  return (int)new_value_long;
}

//---------------------------------------------
// calcDecompressedValue
//---------------------------------------------
float calcDecompressedValue(int b) {
  float f = b / EEPROM_BARO_PRECISION;
  f = f +  EEPROM_MIN_BARO ;
  return f;
}

//---------------------------------------------
// listEepromData
//---------------------------------------------
void listEepromData(){
  // get current EEPROM address
  int addr = getEepromInfo(EEPROM_ADDR_STORED);
  // get current number of data in EEPROM
  int cnt =  getEepromInfo(EEPROM_CNT_STORED);

  if (cnt == 0 ) return;

  int pos=addr;
  for ( int i = 0 ; i < cnt ; i++) {
    float f = calcDecompressedValue( getEepromInfo(pos) );
    Serial.print("POST  ");
    Serial.println(f);
    
    if (graphBufCounter < GRAPH_BUF_SIZE ) {
       P[i] = EEPROM.read(pos + 2) + MID_BARO_MATRIX ;
       T[i] = (int)EEPROM.read(pos + 3) - MID_TEMP_MATRIX ;
       graphBufCounter++;
    }
    pos = pos - 4 ; 
    if ( pos < EEPROM_MIN_ADDR) {
      pos = EEPROM_MAX_ADDR;
    }
  }
  // Serial.println();
  
  // DEBUG START
  Serial.print("DEBUG ");
  for (int i=0 ; i < graphBufCounter ; i++ ) {
    Serial.print(P[i]); 
    Serial.print(" ");
  }
  Serial.println();

  Serial.print("DEBUG ");
  for (int i=0 ; i < graphBufCounter ; i++ ) {
    Serial.print(T[i]); 
    Serial.print(" ");
  }
  Serial.println();
  // DEBUG END
  

}

//---------------------------------------------
// checkAndSwitchSegment
//---------------------------------------------
void checkAndSwitchSegment(){
  switchSegCounter++ ;
  if ( switchSegCounter >  SWITCH_SEG_COUNT ) {
    switchSegCounter=0;
    currentSegID++ ;
    if ( currentSegID > 3) {
      currentSegID = 0 ;
    }
    displayNumber(currentSegID);
  }
}

//---------------------------------------------
// displayNumber
//---------------------------------------------
void displayNumber(int digit) {
  for (int i = 0 ; i < 4 ; i++){
    // turn off all LEDs
    digitalWrite(TR[i], HIGH);
  }
  // set a digit pattern
  shiftOut(DATA_PIN, CLOCK_PIN, LSBFIRST, NUM[SEG[digit]]);
  // turn on single LED
  digitalWrite(TR[digit],LOW);
} 

//---------------------------------------------
// setBufToMatrix
//---------------------------------------------
void setBufToMatrix(int X[] ) {
  matrixColCounter = 0;
  int p = 0;
  for (int i = 0 ; i < graphBufCounter ; i+= SAMPLING_RATE ) {
    M[p++] = X[i];
    matrixColCounter++ ;
  }
}

//---------------------------------------------
// genMatrixPattern
//---------------------------------------------
void genMatrixPattern(){
  
  int max=0;
  int min=0;

  for (int i=0,p=7 ; i < matrixColCounter ; i++,p-- ) {
    if (i==0) {
      max=M[p];
      min=max;
    } else {
      if (max < M[p]) {
        max=M[p];
      }
      if ( min > M[p] ) {
        min=M[p];
      }
    }
  }
  int mid = (max - min ) / 2 + min;

  for ( int i=0 ; i < 8 ; i++ ) {
    Matrix[i] = 0xFF;
  }

  // middle value will be ploted row 4 in matrix
  for (int i=0,p=7 ; i < matrixColCounter ; i++,p-- ) {
    int r = 3 - M[p] + mid ;
    if (r > 7 ) {
      r = 7;
    } else if ( r < 0 ) {
      r = 0;
    }
    Matrix[r] = Matrix[r] ^ (1 << p ); // XOR
  }
}

//---------------------------------------------
// checkAndSwitchMatrixRow
//---------------------------------------------
void checkAndSwitchMatrixRow() 
{
  static int cnt=0;

  if (++switchMatrixCounter < SWITCH_MATRIX_COUNT ) {
    return;
  }

  switchMatrixCounter=0;

  digitalWrite(K_LATCH_PIN , LOW);     // HC595
  shiftOut(K_DATA_PIN, K_CLOCK_PIN, LSBFIRST, 0xFF);
  digitalWrite(K_LATCH_PIN , HIGH);    // HC595
  
  // one bit shift for shift registor #1
  if (cnt == 0 ) {
    digitalWrite(A_DATA_PIN, HIGH);
  } else {
    digitalWrite(A_DATA_PIN, LOW);
  }
  digitalWrite(A_CLOCK_PIN, HIGH);
  digitalWrite(A_CLOCK_PIN, LOW);

  // eight bits shift for shift registor #2
  digitalWrite(K_LATCH_PIN , LOW);      // HC595
  shiftOut(K_DATA_PIN, K_CLOCK_PIN, LSBFIRST, Matrix[cnt]);
  digitalWrite(K_LATCH_PIN , HIGH);     // HC595

  if (++cnt >= 8 ) {
    cnt=0;
  }
}

// 
float read_coefficients(int total_bits, int fractional_bits, int zero_pad) {
  unsigned char msb, lsb;
  
  msb = Wire.read();
  lsb = Wire.read();
  
  return ((float) ((msb << 8) + lsb) / ((long)1 << 16 - total_bits + fractional_bits + zero_pad));
}

unsigned int read_adc() {
  unsigned char msb, lsb;
  msb = Wire.read();
  lsb = Wire.read();
  
  return (((unsigned int)msb << 8) + lsb) >> 6;
}  

